#include "s_norflash_driver.h"

#include <stdio.h>
#include <string.h>
//////////////////////////////////////////////////////////////////////////////////
#define NORFLASH_PAGE_PROGRAM_CMD           0x02
#define NORFLASH_READ_CMD                   0x03
#define NORFLASH_READ_STATUS_CMD            0x05
#define NORFLASH_WRITE_ENABLE_CMD           0x06
#define NORFLASH_SECTOR_ERASE_CMD           0x20
#define NORFLASH_JEDEC_ID_CMD               0x90
#define NORFLASH_POWER_STANDBY_CMD          0xAB
#define NORFLASH_POWER_DOWN_CMD             0xB9
#define NORFLASH_CHIP_ERASE_CMD             0xC7


#define NORFLASH_STATUS_BUSY_BIT            0x01
#define NORFLASH_JEDEC_ID_CMD_SIZE          4
#define NORFLASH_JEDEC_ID_SIZE              2

/* Part specific constants */
// #define norflash.pageSize            256
// #define norflash.sectorSize          4096
//////////////////////////////////////////////////////////////////////////////////

static NorFlash_Result s_norFlash_Struct_Check(NorFlash_Handle norflash)
{
    if(0x00 == norflash.manId || 0x00 == norflash.devId)
    {
        return norflash_id_error;
    }
    if(0x00 == norflash.pageSize)
    {
        return norflash_pagesize_error;
    }
    // if(norflash.sectorSize * norflash.sectorCount != norflash.devSize)
    // {
    //     return norflash_size_not_matched;
    // }
    if(NULL == norflash.norflash_select || NULL == norflash.norflash_deselect || \
        NULL == norflash.norflash_write || NULL == norflash.norflash_read)
    {
        return norflash_function_null_error;
    }

    return norflash_OK;
}

static NorFlash_Result s_norFlash_waitReady(NorFlash_Handle norflash)
{
    unsigned char cmd = NORFLASH_READ_STATUS_CMD;

    NorFlash_Result rslt = norflash_OK;

    // if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    // {
    //  return rslt;
    // }

    for (;;)
    {
        unsigned char rdata;

        norflash.norflash_select();
        norflash.norflash_write(&cmd, sizeof(cmd));
        rslt = norflash.norflash_read(&rdata, sizeof(rdata));

        norflash.norflash_deselect();

        if (rslt)
        {
            /* Error */
            // printf("error\r\n");
            return (rslt);
        }
        if (!(rdata & NORFLASH_STATUS_BUSY_BIT))
        {
            /* Now ready */
            // printf("--success\r\n");
            return norflash_OK;
        }
    }
}

static NorFlash_Result s_norFlash_waitPowerDown(NorFlash_Handle norflash)
{
    unsigned char i = 0;

    while (i++ < 10)
    {
        if (norflash_OK != S_NorFlash_VarifyInfo(norflash, NULL, NULL))
        {
            return norflash_powerdown_success;
        }
    }

    return norflash_powerdown_failed;
}

static NorFlash_Result s_norflash_writeEnable(NorFlash_Handle norflash)
{
    unsigned char cmd = NORFLASH_WRITE_ENABLE_CMD;

    NorFlash_Result rslt = norflash_OK;

    norflash.norflash_select();
    rslt = norflash.norflash_write(&cmd, sizeof(cmd));
    norflash.norflash_deselect();

    return rslt;
}

NorFlash_Result S_NorFlash_VarifyInfo(NorFlash_Handle norflash, unsigned char *manId, unsigned char *devId)
{
    unsigned char wdata[NORFLASH_JEDEC_ID_CMD_SIZE] = {NORFLASH_JEDEC_ID_CMD};
    unsigned char rdata[NORFLASH_JEDEC_ID_SIZE];

    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    norflash.norflash_select();
    norflash.norflash_write(wdata, NORFLASH_JEDEC_ID_CMD_SIZE);
    norflash.norflash_read(rdata, NORFLASH_JEDEC_ID_SIZE);
    norflash.norflash_deselect();

    if(manId != NULL)
    {
        *manId = rdata[0];
    }
    if(devId != NULL)
    {
        *devId = rdata[1];
    }
    

    if(norflash.manId == rdata[0] && norflash.devId == rdata[1])
    {
        return norflash_OK;
    }
    else
    {
        // printf("the manid is 0x%02x, devid is 0x%02x\r\n", rdata[0], rdata[1]);
        return norflash_varify_failed;
    }
    
}

NorFlash_Result S_NorFlash_PowerStandby(NorFlash_Handle norflash)
{
    unsigned char cmd = NORFLASH_POWER_STANDBY_CMD;

    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }


    norflash.norflash_select();
    rslt = norflash.norflash_write(&cmd, sizeof(cmd));
    norflash.norflash_deselect();

    if(norflash_OK == rslt)
    {
        unsigned short i = 200;

        // Waking up of the device is manufacturer dependent.
        // for a Winond chip-set, once the request to wake up the flash has been
        // send, CS needs to stay high at least 3us (for Winbond part)
        // for chip-set like Macronix, it can take up to 35us.
        while(i--);

        if (s_norFlash_waitReady(norflash) != norflash_OK)
        {
            rslt = norflash_powerstandby_failed;
        }   
    }

    return rslt;
}

NorFlash_Result S_NorFlash_PowerDown(NorFlash_Handle norflash)
{
    unsigned char cmd = NORFLASH_POWER_DOWN_CMD;

    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }


    norflash.norflash_select();
    rslt = norflash.norflash_write(&cmd, sizeof(cmd));
    norflash.norflash_deselect();

    if(norflash_OK == rslt)
    {
        rslt = s_norFlash_waitPowerDown(norflash);
    }

    if(rslt == norflash_powerdown_success)
    {
        return norflash_OK;
    }
    return rslt;
}

NorFlash_Result S_NorFlash_Open(NorFlash_Handle norflash)
{
    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    norflash.norflash_deselect();

    if((rslt = S_NorFlash_PowerStandby(norflash)) == norflash_OK)
    {
        rslt = S_NorFlash_VarifyInfo(norflash, NULL, NULL);
    }
    else
    {
        //TODO flash close
        rslt = S_NorFlash_Close(norflash);
    }

    return rslt;

}

NorFlash_Result S_NorFlash_Close(NorFlash_Handle norflash)
{
    return S_NorFlash_PowerDown(norflash);
}

NorFlash_Result S_NorFlash_Read(NorFlash_Handle norflash, unsigned int offset, unsigned char *buf, unsigned short length)
{
    unsigned char wdata[4];

    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    /* Wait till previous erase/program operation completes */
    rslt = s_norFlash_waitReady(norflash);
    if (rslt != norflash_OK)
    {
        return norflash_waitready_failed;
    }

    /* SPI is driven with very low frequency (1MHz < 33MHz fR spec)
    * in this temporary implementation.
    * and hence it is not necessary to use fast read. */
    wdata[0] = NORFLASH_READ_CMD;
    wdata[1] = (offset >> 16) & 0xff;
    wdata[2] = (offset >> 8) & 0xff;
    wdata[3] = offset & 0xff;

    norflash.norflash_select();

    // printf("ssd\r\n");

    if (norflash.norflash_write(wdata, sizeof(wdata)) != norflash_OK)
    {
        /* failure */
        norflash.norflash_deselect();
        return norflash_write_failed;
    }

    rslt = norflash.norflash_read(buf, length);

    norflash.norflash_deselect();

    return norflash_OK;
}

NorFlash_Result S_NorFlash_Write(NorFlash_Handle norflash, unsigned int offset, unsigned char *buf, unsigned short length)
{
    unsigned char wdata[4];

    NorFlash_Result rslt = norflash_OK;

    unsigned short ilen; /* interim length per instruction */

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    while (length > 0)
    {
        // printf("write len :%d\n", length);

        /* Wait till previous erase/program operation completes */
        rslt = s_norFlash_waitReady(norflash);
        if (rslt != norflash_OK)
        {
            return norflash_waitready_failed;
        }

        // printf("1\n");

        rslt = s_norflash_writeEnable(norflash);
        if (rslt)
        {
            return norflash_write_failed;
        }
        // printf("2\n");
        

        ilen = norflash.pageSize - (offset % norflash.pageSize);
        if (length < ilen)
        {
            ilen = length;
        }
        // printf("ilen:%d\r\n", ilen);

        // printf("--len is %d, offset is %d\r\n", ilen, offset);

        wdata[0] = NORFLASH_PAGE_PROGRAM_CMD;
        wdata[1] = (offset >> 16) & 0xff;
        wdata[2] = (offset >> 8) & 0xff;
        wdata[3] = offset & 0xff;

        offset += ilen;
        length -= ilen;

        /* Up to 100ns CS hold time (which is not clear
        * whether it's application only in between reads)
        * is not imposed here since above instructions
        * should be enough to delay
        * as much. */
        norflash.norflash_select();

        // printf("3\n");


        if (norflash.norflash_write(wdata, sizeof(wdata)) != norflash_OK)
        {
            /* failure */
            norflash.norflash_deselect();
            return norflash_write_failed;
        }
        // printf("4\n");

        // debug_hex(buf, ilen);
        // printf("0x%02x%02x\n", buf[0], buf[1]);

        if (norflash.norflash_write(buf, ilen) != norflash_OK)
        {
            /* failure */
            norflash.norflash_deselect();
            return norflash_write_failed;
        }
        buf += ilen;
        norflash.norflash_deselect();

        // printf("5\n");
    }

    return norflash_OK;

} 

NorFlash_Result S_NorFlash_Erase(NorFlash_Handle norflash, unsigned int offset, unsigned short length)
{
    /* Note that Block erase might be more efficient when the floor map
    * is well planned for OTA but to simplify for the temporary implemetation,
    * sector erase is used blindly. */
    unsigned char wdata[4];
    unsigned int i, numsectors;

    
    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    unsigned int endoffset = offset + length - 1;
    offset = (offset / norflash.sectorSize) * norflash.sectorSize;
    numsectors = (endoffset - offset + norflash.sectorSize - 1) /
                  norflash.sectorSize;

    // printf("--erase numsectors %d\r\n", numsectors);

    for (i = 0; i < numsectors; i++)
    {
        /* Wait till previous erase/program operation completes */
        rslt = s_norFlash_waitReady(norflash);
        if (rslt)
        {
            return rslt;
        }

        rslt = s_norflash_writeEnable(norflash);
        if (rslt)
        {
            return norflash_write_failed;
        }

        wdata[0] = NORFLASH_SECTOR_ERASE_CMD;
        wdata[1] = (offset >> 16) & 0xff;
        wdata[2] = (offset >> 8) & 0xff;
        wdata[3] = offset & 0xff;

        norflash.norflash_select();

        if (norflash.norflash_write(wdata, sizeof(wdata)) != norflash_OK)
        {
            /* failure */
            norflash.norflash_deselect();
            return norflash_write_failed;
        }

        norflash.norflash_deselect();

        offset += norflash.sectorSize;
    }

    return norflash_OK;
}

NorFlash_Result S_NorFlash_ChipErase(NorFlash_Handle norflash)
{
    unsigned char cmd = NORFLASH_CHIP_ERASE_CMD;
    NorFlash_Result rslt = norflash_OK;

    if((rslt = s_norFlash_Struct_Check(norflash)) != norflash_OK)
    {
        return rslt;
    }

    /* Wait till previous erase/program operation completes */
    rslt = s_norFlash_waitReady(norflash);
    if (rslt)
    {
        return rslt;
    }

    rslt = s_norflash_writeEnable(norflash);
    if (rslt)
    {
        return norflash_write_failed;
    }

    norflash.norflash_select();

    if (norflash.norflash_write(&cmd, sizeof(cmd)) != norflash_OK)
    {
        /* failure */
        norflash.norflash_deselect();
        return norflash_write_failed;
    }

    norflash.norflash_deselect();

    /* Wait till previous erase/program operation completes */
    rslt = s_norFlash_waitReady(norflash);
    if (rslt)
    {
        return rslt;
    }

    return norflash_OK;
}


